use app::*;
use axum::{
    async_trait, extract::{ FromRef, FromRequestParts, Host, Query, State, Request}, http::{header::{HeaderMap, SET_COOKIE}, request::Parts, StatusCode}, response::{IntoResponse, Json, Redirect, Response}, Form, RequestPartsExt
};

use axum::{
    error_handling::HandleErrorLayer,
    extract::DefaultBodyLimit,    
  //  response::Html,
    middleware::{self, Next},
    body::Body,
    http::Uri,
    routing::{get, post, Router},
};
use axum::response::Result as AResult;
use http::header::{HeaderValue};

use serde::{Deserialize, Serialize, *};
use fileserv::file_and_error_handler;
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use std::path::{self, PathBuf};
use tower_http::services::ServeDir;
use tower::{BoxError, ServiceBuilder};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;
use std::time::Duration;
use std::borrow::Cow;
use std::sync::Arc;
use tokio;
use tokio::signal;
use tower::ServiceExt;
use tower_http::validate_request::{ValidateRequest, ValidateRequestHeader, ValidateRequestHeaderLayer};
use tracing::{debug, info};
use anyhow::{bail, Result};
type SharedHandle = Arc<Shared>;
use chrono::prelude::*;
use common::{Config, Task, SearchParam};

pub mod fileserv;
mod api;
mod db;
mod shared;
use api::*;
use db::*;
use shared::*;

/* 
pub struct Shared {    
    pub timestamp : std::sync::RwLock<String>,
    pub dataset : std::sync::RwLock< Vec<Task> >,
    pub site_root: String,
}
*/ 

#[tokio::main]
async fn main()-> Result<()> {

    let cfg_file = "./hello.json";
    let cfg:Config = serde_json::from_str( String::from_utf8(std::fs::read(&cfg_file).unwrap()).unwrap().as_str() ).unwrap();  

    simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");

    // Setting get_configuration(None) means we'll be using cargo-leptos's env values
    // For deployment these variables are:
    // <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
    // Alternately a file can be specified such as Some("Cargo.toml")
    // The file would need to be included with the executable when moved to deployment
    
    let mut conf = get_configuration(None).await.unwrap();
        
    conf.leptos_options.output_name = cfg.pkg_name.clone(); // "start-axum".to_owned();    
    conf.leptos_options.site_root = cfg.site_root.clone(); // "./target/site".to_owned();
    conf.leptos_options.site_pkg_dir = cfg.site_pkg.clone(); // "site/pkg".to_owned();
    
    // 
    let dbpath = PathBuf::from(&cfg.site_root).join("db");
    if dbpath.exists() == false {
         std::fs::create_dir_all(dbpath).unwrap();
    }
    let database_name = format!("sqlite:{}{}db{}hello.db", cfg.site_root, std::path::MAIN_SEPARATOR, std::path::MAIN_SEPARATOR);
    let dbpool = db::create_db_pool(&database_name).await?;
       

println!("{:#?}\n{:#?}", cfg, conf);

    let leptos_options = conf.leptos_options;
    let addr =  cfg.site_addr;
    let routes = generate_route_list(App);

    let assets_dir = PathBuf::from(&cfg.site_root); //   leptos_options.site_root.as_str());
    //let server_uri = "http://192.168.2.100:3000";
    let shared = Shared::new(cfg_file, &cfg.server_uri, &cfg.site_root , dbpool);
    let shared: Arc<Shared> = Arc::new( shared );

    let app_state = leptos_options.clone();

    // API funcs
    let api_routes = Router::new()     
        .route("/get_api_hello", get(get_api_hello))   
        .route("/chat_glm", post(post_chat_glm))
        .route("/get_glm_chat", get(get_glm_chat))
        .route("/add_chat_glm", post(new_chat_glm))
        .route("/grep_search", get(grep_search));
             
    // build our application with a route
    let app = Router::new()    
    // 特别注意，axum的路由等功能注册，都必须在leptos_axum之前，否则会报错！！！
        .route("/post_hello", post(post_todo))
        .route("/get_hello", get(get_hello))
        .route("/axum/post_task", post(api_post_task))
        .route("/axum/get_task", get(api_get_task))
        .route("/favicon.ico", get( spec_res_handler) )
       // .nest("/api", api_routes.route_layer(ValidateRequestHeaderLayer::bearer("abc")))
        .nest("/api", api_routes.route_layer(middleware::from_fn(my_auth)))
        .with_state(Arc::clone(&shared))
        .layer(
            ServiceBuilder::new()
                // Handle errors from middleware
                .layer(HandleErrorLayer::new(handle_error))
                .load_shed()
                .concurrency_limit(1024)
                .timeout(Duration::from_secs(30))
                .layer(TraceLayer::new_for_http())
                .into_inner(),
        )
        .layer(DefaultBodyLimit::disable())
        .layer(RequestBodyLimitLayer::new(
            10 * 1024 * 1024, /* 250mb */
        ))              
        .nest_service("/site", ServeDir::new(assets_dir))    
        // 此处之下，才是leptos_axum注册的！！！
        //.leptos_routes(&leptos_options, routes, App)          
    	.leptos_routes_with_context(
            &leptos_options,
            routes,
            {  let app_state = app_state.clone();
                move || provide_context(app_state.clone())
            },
            App,
        )
        .fallback(file_and_error_handler)  
        .with_state(leptos_options);       
       
    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    log::info!("listening on http://{}", &addr);
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
     
    /*  
    axum::serve(listener, app.into_make_service())
        .await
        .unwrap();    
    */ 
    let r = axum::serve(listener, app.into_make_service())
        // 因为被leptos 封装，这里的with_graceful_shutdown实际并不起作用！！！！
        .with_graceful_shutdown(shutdown_signal("auxum server"))
        .await;

    if let std::result::Result::Err(err) = r {
        println!("{:#?}", err);
        //  bail!("create auxum web server failed")
    } 
    Ok(())
}

async fn my_auth(mut req: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get(http::header::AUTHORIZATION)
        .and_then(|header| header.to_str().ok());

    let auth_header = if let Some(auth_header) = auth_header {
        auth_header
    } else {
        return Err(StatusCode::UNAUTHORIZED);
    };

    if authorize_token(auth_header).await {
        // insert the current user into a request extension so the handler can
        // extract it
        // req.extensions_mut().insert(current_user);
        Ok(next.run(req).await)
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

async fn authorize_token(auth_token: &str) -> bool {
    debug!("authorize_token: {}", auth_token);
    /*

    if auth_token.len() <= timestamp_size {
        return false;
    } 

    let token_md = auth_token[..timestamp_size].to_string();
    let token_time = auth_token[timestamp_size..]::parse::<i64>().unwrap();
    let now = Utc::now();
    let md_new = md5::compute(format!("{}{}", token_time, SECRET_KEY));

    if token_md == md_new.to_string() && now.timestamp() - token_time < 10 {     
        return true;
    }

    return false;

    */
    return true;
}

async fn shutdown_signal(name: &str) {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }   
}


async fn handle_error(error: BoxError) -> impl IntoResponse {
    if error.is::<tower::timeout::error::Elapsed>() {
        return (
            StatusCode::REQUEST_TIMEOUT,
            Cow::from("request timed out  [ handle_error ]"),
        );
    }

    if error.is::<tower::load_shed::error::Overloaded>() {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Cow::from("service is overloaded, try again later  [ handle_error ]"),
        );
    }

    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Cow::from(format!(
            "!!!!!!!!!! Unhandled internal error: {} [ handle_error ]",
            error
        )),
    )
}

async fn spec_res_handler(  uri: Uri, State(shared): State<SharedHandle>,) ->  AResult<Response> {
    logging::log!("favicon_handler: {}/{}", shared.site_root, uri);
    let site =  path::PathBuf::from(&shared.site_root);    
    let req = Request::builder()
        .uri(uri.clone())
        .body(Body::empty())
        .unwrap();
    let res = ServeDir::new(&site).oneshot(req).await?;
    return Ok(res.map(Body::new).into_response());
}
